docs(auth): align front-door flows with shipped workflows#80
docs(auth): align front-door flows with shipped workflows#80ndycode wants to merge 15 commits intogit-plan/05-settings-productizationfrom
Conversation
…plan/06-docs-frontdoor-cleanup
…cs-frontdoor-cleanup # Conflicts: # test/codex-manager-cli.test.ts
📝 Walkthroughwalkthroughthis pr adds interactive backup-restore and preview-first sync workflows, updates docs across the project, and exposes utilities to detect interactive login availability and to enumerate actionable named backups. the login flow now may prompt to restore before OAuth and suppresses prompts for explicit destructive actions ( changes
sequence diagram(s)sequenceDiagram
participant user as User
participant cli as CLI
participant login as LoginFlow
participant storage as BackupStorage
participant assess as BackupAssessment
participant oauth as OAuth
user->>cli: codex auth login
cli->>login: start interactive login
login->>storage: getActionableNamedBackupRestores()
storage->>assess: assess backups (concurrent)
assess-->>storage: assessment results
storage-->>login: {assessments, totalBackups}
alt actionable backups exist
login->>user: prompt "Restore From Backup"
user->>login: choose restore or skip
alt restore
login->>storage: run selected restore
storage-->>login: restore complete
else skip
login->>oauth: proceed to OAuth
end
else no actionable backups
login->>oauth: proceed to OAuth
end
oauth-->>user: complete login
estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes suggested labels
additional flags
🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
1 issue found across 13 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="test/recovery.test.ts">
<violation number="1" location="test/recovery.test.ts:1224">
P2: This new test suite is skipped, so the added backup-restore assertions never run. Use an active `describe` (or add explicit justification if this must remain skipped).</violation>
</file>
Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.
Wrap getActionableNamedBackupRestores in try-catch so a filesystem error (e.g. antivirus holding a backup file on Windows) skips the recovery prompt instead of crashing the login flow. Switch Promise.all to Promise.allSettled in getActionableNamedBackupRestores so one unreadable backup file does not block assessment of the rest.
Verify that when getActionableNamedBackupRestores throws EBUSY the login flow falls through to OAuth instead of crashing.
…ion' into git-plan/06-docs-frontdoor-cleanup # Conflicts: # test/codex-manager-cli.test.ts
There was a problem hiding this comment.
Pull request overview
Aligns the “front door” docs and interactive codex auth login flows with shipped workflows by surfacing startup recovery/restore, Codex CLI sync center, and the new Everyday vs Advanced/Operator settings split.
Changes:
- Adds a startup recovery prompt (TTY-only) that offers backup restore before OAuth when actionable named backups exist.
- Introduces sync-center preview/run tracking and exposes watcher/sync snapshots for UI surfaces.
- Refreshes README + docs to highlight restore, sync center, settings split, and related troubleshooting guidance.
Reviewed changes
Copilot reviewed 13 out of 13 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| test/settings-hub-utils.test.ts | Adds coverage for sync-center overview text and improves test formatting. |
| test/recovery.test.ts | Reformats tests and adds coverage for actionable named backup restore filtering. |
| test/live-account-sync.test.ts | Adds coverage for publishing watcher snapshot state used by sync-center/status UIs. |
| test/codex-manager-cli.test.ts | Adds coverage for startup restore prompt, TTY gating, suppress-after-reset, settings split, and sync-center UX. |
| test/codex-cli-sync.test.ts | Expands sync tests to cover preview summaries and last-run tracking. |
| lib/ui/copy.ts | Productizes settings copy (Everyday vs Advanced & Operator) and adds sync-center UI strings. |
| lib/storage.ts | Adds getActionableNamedBackupRestores() using Promise.allSettled to isolate per-backup read failures. |
| lib/live-account-sync.ts | Publishes a module-level snapshot for sync-center/status surfaces and adds test reset helper. |
| lib/codex-manager.ts | Adds startup recovery prompt path before OAuth, with suppression on reset/fresh actions and try/catch fallback. |
| lib/codex-cli/sync.ts | Refactors reconciliation into helper, adds preview API, and tracks “last sync run” in module state. |
| lib/cli.ts | Adds isInteractiveLoginMenuAvailable() gate and uses it for fallback login mode. |
| docs/troubleshooting.md | Adds restore + sync troubleshooting sections and front-door guidance. |
| docs/reference/settings.md | Documents Everyday vs Advanced & Operator settings and the sync-center workflow. |
| docs/reference/commands.md | Updates codex auth login description and lists interactive workflow packs. |
| docs/index.md | Updates landing page to mention restore and sync workflows. |
| docs/getting-started.md | Adds startup restore prompt guidance + restore/start-fresh and sync/settings sections. |
| docs/features.md | Updates capabilities list for startup prompt, restore manager, settings split, and sync center. |
| docs/README.md | Updates docs portal index descriptions to match new workflows. |
| README.md | Updates project overview and common workflows to surface restore + sync center. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| const backupLabel = | ||
| sample?.backup.name ?? | ||
| `${recoveryState.assessments.length} backup${ | ||
| recoveryState.assessments.length === 1 ? "" : "s" | ||
| }`; | ||
| const restoreNow = await confirm( | ||
| `Found ${recoveryState.assessments.length} recoverable backup${ | ||
| recoveryState.assessments.length === 1 ? "" : "s" | ||
| } (${backupLabel}) in ${backupDir}. Restore now?`, | ||
| ); |
| const authModule = await import("../lib/auth/auth.js"); | ||
| const createAuthorizationFlowMock = vi.mocked( | ||
| authModule.createAuthorizationFlow, | ||
| ); | ||
| createAuthorizationFlowMock.mockRejectedValue( | ||
| new Error("oauth flow should be skipped when restoring backup"), | ||
| ); |
There was a problem hiding this comment.
Actionable comments posted: 6
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@docs/reference/commands.md`:
- Around line 99-101: Update the "Startup recovery prompt" entry to state that
the restore prompt appears only during interactive/TTY logins (i.e., when the
interactive login flow is used), matching the runtime guard in
lib/codex-manager.ts:4373; specifically note the tty/interactive requirement so
docs reflect shipped behavior and add an upgrade note if this is a behavior
change.
In `@lib/cli.ts`:
- Around line 29-31: The isInteractiveLoginMenuAvailable function redundantly
calls isTTY(); simplify it by relying solely on isNonInteractiveMode(): change
isInteractiveLoginMenuAvailable to return !isNonInteractiveMode() only. Update
the function (isInteractiveLoginMenuAvailable) and remove the extra isTTY()
dependency so the check is not duplicated (keep isNonInteractiveMode and isTTY
implementations unchanged).
In `@lib/codex-manager.ts`:
- Around line 4383-4386: The catch block in the backup assessment (in
lib/codex-manager.ts) currently swallows all errors; change it to catch the
thrown error (e.g., catch (err)) and only treat filesystem lock errors by
checking err.code === 'EPERM' || err.code === 'EBUSY' (or the
platform-equivalent) and set recoveryState = { assessments: [], totalBackups: 0
} for those cases; for any other error rethrow it so real failures surface. Also
update/add vitest tests referenced around test/codex-manager-cli.test.ts:570 to
assert that non-errno exceptions are not treated as the Windows lock fallback
(i.e., expect them to propagate or be handled differently) and include coverage
for EBUSY/EPERM paths.
In `@lib/storage.ts`:
- Around line 1340-1343: The check using options.currentStorage === undefined
distinguishes "option omitted" from an explicit null value; add an inline
comment next to the assignment of currentStorage (the ternary using
options.currentStorage and loadAccounts()) explaining that undefined means
"auto-load via loadAccounts()" while null is a valid explicit value meaning "no
current storage", so future maintainers understand the intentional undefined vs
null semantics for options.currentStorage and the resulting currentStorage
variable.
- Around line 1328-1367: Add a unit test for getActionableNamedBackupRestores
that stubs assessNamedBackupRestore (used via the assess parameter) so some
calls reject with an error (e.g., simulate EBUSY) while others resolve to valid
assessment objects; call getActionableNamedBackupRestores with backups covering
both cases and a mocked currentStorage, then assert the returned assessments
only include the fulfilled results (i.e., rejected assessments are dropped) and
totalBackups equals the original backups length to verify the Promise.allSettled
handling and the filtering logic in getActionableNamedBackupRestores.
In `@test/codex-manager-cli.test.ts`:
- Around line 570-591: Add two deterministic vitest cases mirroring the existing
EBUSY test: (1) an EPERM case that should fall through to OAuth—create an Error
with code "EPERM", have
getActionableNamedBackupRestoresMock.mockRejectedValueOnce(epperm), set
selectMock.mockResolvedValueOnce("cancel"), call
runCodexMultiAuthCli(["auth","login"]) and assert exitCode === 0,
getActionableNamedBackupRestoresMock called once, confirmMock not called, and
createAuthorizationFlowMock called once (same behavior as the EBUSY test); (2) a
generic non-errno error case that must NOT be treated as the Windows lock
fallback—mock getActionableNamedBackupRestoresMock.mockRejectedValueOnce(new
Error("boom")) and assert the error propagates (or exitCode indicates failure)
and that createAuthorizationFlowMock is not invoked; locate tests around
runCodexMultiAuthCli and reference lib/codex-manager.ts:4383-4386 for the
errno-based fallback logic when adding these assertions.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: e5b6b9af-bcad-4291-8465-3112b22b7017
📒 Files selected for processing (13)
README.mddocs/README.mddocs/features.mddocs/getting-started.mddocs/index.mddocs/reference/commands.mddocs/reference/settings.mddocs/troubleshooting.mdlib/cli.tslib/codex-manager.tslib/storage.tstest/codex-manager-cli.test.tstest/recovery.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (3)
docs/**
⚙️ CodeRabbit configuration file
keep README, SECURITY, and docs consistent with actual CLI flags and workflows. whenever behavior changes, require updated upgrade notes and mention new npm scripts.
Files:
docs/troubleshooting.mddocs/index.mddocs/reference/commands.mddocs/reference/settings.mddocs/README.mddocs/getting-started.mddocs/features.md
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/cli.tslib/storage.tslib/codex-manager.ts
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/codex-manager-cli.test.tstest/recovery.test.ts
🪛 LanguageTool
docs/getting-started.md
[style] ~77-~77: To make your writing flow more naturally, try moving the adverb ‘already’ closer to the verb ‘named’.
Context: ...rt Fresh Use the restore path when you already have named backup files and want to recover accoun...
(PERF_TENS_ADV_PLACEMENT)
🔇 Additional comments (19)
docs/features.md (2)
12-13: lgtm - capability entries align with shipped flows.startup recovery prompt and backup restore manager match the interactive recovery path in
lib/codex-manager.tsand thegetActionableNamedBackupRestoresexport inlib/storage.ts.
59-60: lgtm - settings split and sync center entries are accurate.matches the productized settings surface described in
docs/reference/settings.mdand the sync preview workflows.lib/cli.ts (1)
258-260: lgtm - guard replacement is correct.switching from direct
isTTY()toisInteractiveLoginMenuAvailable()centralizes the gating logic and aligns with the exported helper used bylib/codex-manager.tsfor interactive recovery prompts.lib/storage.ts (1)
529-532: lgtm - clean interface for actionable recovery results.the
ActionableNamedBackupRecoveriesinterface properly separates actionable assessments from total backup count, which enables the ui to show "n of m backups recoverable" messaging.docs/index.md (2)
15-16: lgtm - restore prompt note is accurate.matches the interactive recovery flow gated by
isInteractiveLoginMenuAvailable()inlib/cli.ts:29-31and the backup detection inlib/codex-manager.ts.
44-48: lgtm - interactive workflow entries are clear.these three workflows (backup restore, sync preview, settings split) align with the shipped dashboard ux documented in
docs/reference/settings.mdanddocs/features.md.docs/getting-started.md (3)
52-53: lgtm - restore prompt behavior documented accurately.the description matches the interactive startup recovery flow: named backups in
~/.codex/multi-auth/backups/with no active accounts triggers the prompt. this aligns withlib/codex-manager.tsinteractive recovery logic.
75-85: lgtm - restore section paths are correct.backup location
~/.codex/multi-auth/backups/<name>.jsonmatcheslib/storage.ts:44-45(NAMED_BACKUP_DIRECTORY = "backups") and the path construction inlib/storage.ts:464-466.
87-95: lgtm - sync and settings section is clear.the everyday vs advanced split and codex cli sync behavior description matches
docs/reference/settings.md.docs/README.md (2)
20-26: lgtm - portal descriptions updated consistently.the user guide descriptions accurately reflect the new restore, sync, and settings content added to each referenced document.
40-41: lgtm - reference section descriptions updated.commands.md and settings.md descriptions align with their updated content covering interactive entry points and the everyday/advanced split.
docs/troubleshooting.md (3)
21-23: lgtm - restore prompt behavior notes are accurate.correctly describes: (1) interactive terminal gating via
isInteractiveLoginMenuAvailable()inlib/cli.ts:29-31, (2) suppression after intentional reset flows, and (3) manual path via "restore from backup" menu option.
62-70: lgtm - backup restore troubleshooting table is helpful.the symptom/cause/action entries correctly reference the backup location (
~/.codex/multi-auth/backups/) and the filtering behavior fromlib/storage.ts:1358-1364(valid, !wouldExceedLimit, imported > 0).
83-93: lgtm - sync troubleshooting section covers key cases.the one-way sync behavior, destination-only preservation, and backup rollback context descriptions align with the sync preview workflow documented in
docs/reference/settings.md.docs/reference/settings.md (3)
24-24: lgtm - everyday settings intro updated.accurately reflects that the shipped menu defaults to everyday settings as the primary path.
83-88: lgtm - workflow notes clarify sync semantics.important documentation: (1) sync is one-way not bidirectional, (2) target-only accounts are preserved. these match the sync preview behavior described in the pr objectives.
187-187: lgtm - storage backups recommendation added.aligns with the rollback context described in the sync preview workflow and the backup/recovery flows added in this pr.
test/recovery.test.ts (1)
1224-1290: good actionable-backup filtering coveragenice focused regression at
test/recovery.test.ts:1224validatinggetActionableNamedBackupRestoresonly returns actionable assessments and still counts total backups.README.md (1)
103-107: workflow paths are aligned with current cli behaviorthe restore and sync-center entry points described here match the actual flow in
lib/codex-manager.ts:4304-4306andlib/codex-manager.ts:4369-4404.Also applies to: 120-127
| export function isInteractiveLoginMenuAvailable(): boolean { | ||
| return !isNonInteractiveMode() && isTTY(); | ||
| } |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
minor: redundant tty check.
isNonInteractiveMode() at lib/cli.ts:20 already returns true when !input.isTTY || !output.isTTY. the subsequent isTTY() call re-checks the same condition via lib/ui/ansi.ts:80-83.
this may be intentional defense-in-depth if input/output from node:process could differ from process.stdin/process.stdout in some edge runtime, but worth confirming. if redundant, consider simplifying to just !isNonInteractiveMode().
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@lib/cli.ts` around lines 29 - 31, The isInteractiveLoginMenuAvailable
function redundantly calls isTTY(); simplify it by relying solely on
isNonInteractiveMode(): change isInteractiveLoginMenuAvailable to return
!isNonInteractiveMode() only. Update the function
(isInteractiveLoginMenuAvailable) and remove the extra isTTY() dependency so the
check is not duplicated (keep isNonInteractiveMode and isTTY implementations
unchanged).
There was a problem hiding this comment.
Actionable comments posted: 3
♻️ Duplicate comments (2)
lib/codex-manager.ts (1)
4389-4392:⚠️ Potential issue | 🟠 Majornarrow recovery fallback catch to windows lock errors only.
lib/codex-manager.ts:4389still swallows every failure from backup assessment. this hides non-filesystem regressions and silently falls through to oauth. onlyeperm/ebusyshould be treated as fallback; rethrow everything else.proposed fix
- } catch { - // Filesystem error (e.g. Windows EPERM/EBUSY) – skip recovery prompt - recoveryState = { assessments: [], totalBackups: 0 }; - } + } catch (error) { + const code = (error as NodeJS.ErrnoException | undefined)?.code; + if (code === "EPERM" || code === "EBUSY") { + // windows filesystem lock edge case: continue with oauth + recoveryState = { assessments: [], totalBackups: 0 }; + } else { + throw error; + } + }#!/bin/bash # confirm catch scope in login recovery path rg -n -C3 'getActionableNamedBackupRestores|catch \(|EPERM|EBUSY|recoveryState = \{ assessments: \[\], totalBackups: 0 \}' lib/codex-manager.ts # confirm vitest coverage for windows fallback and non-errno behavior rg -n -C4 'getActionableNamedBackupRestores|EPERM|EBUSY|non-errno|throw|reject|recovery' test/codex-manager-cli.test.tsexpected result: tests should show
eperm/ebusyfallthrough behavior and a non-errno case that does not get swallowed.as per coding guidelines
lib/**: focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@lib/codex-manager.ts` around lines 4389 - 4392, The catch block that currently swallows all errors around backup assessment must be narrowed to only handle Windows lock errors: change the bare catch to catch the error object (e.g., catch (err)) and test err.code (or err.errno) for 'EPERM' or 'EBUSY'; if it matches, set recoveryState = { assessments: [], totalBackups: 0 } as before, otherwise rethrow the error so non-filesystem regressions surface. Locate this change around the backup assessment / getActionableNamedBackupRestores call and the existing recoveryState assignment to implement the conditional handling and ensure other errors are not swallowed.test/codex-manager-cli.test.ts (1)
570-591:⚠️ Potential issue | 🟠 Majorcover both remaining backup-assessment error branches.
at
test/codex-manager-cli.test.ts:570-591, this only locks in theebusybranch fromlib/codex-manager.ts:4383-4386. we still need deterministic regressions forepermfalling through to oauth on windows, and for a plainError("boom")proving the fallback is errno-gated and does not swallow unrelated failures. without both, a broad catch can regress the login front door again. as per coding guidelinestest/**: tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@lib/codex-manager/settings-hub.ts`:
- Around line 2697-2700: Add a vitest unit test that exercises promptSyncCenter
and verifies withQueuedRetry retries saveAccounts on transient errors: mock
saveAccounts (or saveAccountsMock used by the sync-center flow) to first reject
with a retryable error code (EBUSY or EPERM) and then resolve on the next call,
invoke promptSyncCenter to apply changes (so the code path at the call site
using withQueuedRetry(getStoragePath(), () => saveAccounts(syncedStorage))
runs), and assert saveAccounts was called at least twice and the final outcome
succeeds; ensure the mock resets afterward and use the same helper
utilities/stubs used by existing tests for dashboard/plugin flows to mirror
their retry coverage pattern.
In `@test/codex-manager-cli.test.ts`:
- Around line 1383-1464: The test currently only verifies the menu flow but
doesn't assert the actual backup restore side-effect; update the test to assert
that restoreNamedBackupMock was invoked and/or that the in-memory storageState
was updated after runCodexMultiAuthCli(["auth","login"]) completes (e.g.,
expect(restoreNamedBackupMock).toHaveBeenCalled() and
expect(storageState?.accounts?.[0]?.email).toBe("restored@example.com")); locate
the assertions around runCodexMultiAuthCli in the "offers backup recovery..."
test and add these checks to ensure the restore path executed end-to-end and not
just the UI prompts.
In `@test/recovery.test.ts`:
- Around line 328-334: Replace vi.restoreAllMocks() in the afterEach block with
vi.resetAllMocks() so mocked implementations (e.g.,
mockedReadParts.mockReturnValue(...) set in tests at lines referenced) are reset
between tests; keep the beforeEach vi.clearAllMocks() if you want to preserve
call-history clearing, but ensure vi.resetAllMocks() runs in afterEach to
restore mock implementations to their original state.
---
Duplicate comments:
In `@lib/codex-manager.ts`:
- Around line 4389-4392: The catch block that currently swallows all errors
around backup assessment must be narrowed to only handle Windows lock errors:
change the bare catch to catch the error object (e.g., catch (err)) and test
err.code (or err.errno) for 'EPERM' or 'EBUSY'; if it matches, set recoveryState
= { assessments: [], totalBackups: 0 } as before, otherwise rethrow the error so
non-filesystem regressions surface. Locate this change around the backup
assessment / getActionableNamedBackupRestores call and the existing
recoveryState assignment to implement the conditional handling and ensure other
errors are not swallowed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4f623c18-d231-4cfc-b850-48f96ba22f5a
📒 Files selected for processing (5)
lib/codex-manager.tslib/codex-manager/settings-hub.tslib/storage.tstest/codex-manager-cli.test.tstest/recovery.test.ts
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: cubic · AI code reviewer
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (2)
test/**
⚙️ CodeRabbit configuration file
tests must stay deterministic and use vitest. demand regression cases that reproduce concurrency bugs, token refresh races, and windows filesystem behavior. reject changes that mock real secrets or skip assertions.
Files:
test/recovery.test.tstest/codex-manager-cli.test.ts
lib/**
⚙️ CodeRabbit configuration file
focus on auth rotation, windows filesystem IO, and concurrency. verify every change cites affected tests (vitest) and that new queues handle EBUSY/429 scenarios. check for logging that leaks tokens or emails.
Files:
lib/storage.tslib/codex-manager.tslib/codex-manager/settings-hub.ts
🪛 ast-grep (0.41.1)
lib/codex-manager.ts
[warning] 725-728: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
${String.fromCharCode(0x1b)}\\[[0-9;]*m|[${String.fromCharCode(0x00)}-${String.fromCharCode(0x1f)}${String.fromCharCode(0x7f)}],
"g",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html
(regexp-from-variable)
🔇 Additional comments (6)
lib/storage.ts (2)
529-532: clean public contract for actionable recovery state.the exported shape in
lib/storage.ts:529is clear and matches the startup recovery flow expectations.
1328-1379: good windows-resilient assessment fan-out and filtering.
lib/storage.ts:1345usespromise.allsettledcorrectly, andlib/storage.ts:1360safely drops rejected assessments so one locked/unreadable backup does not block the rest.lib/codex-manager.ts (4)
26-27: imports align with the shipped startup recovery flow.the added imports at
lib/codex-manager.ts:26andlib/codex-manager.ts:80cleanly wire interactive gating and actionable backup assessment into login.Also applies to: 80-81
725-729: good terminal-safety hardening for displayed labels.the sanitizer regex in
lib/codex-manager.ts:725is a static pattern and is appropriate for stripping ansi/control chars from user-visible text.
4187-4188: recovery suppression state is correctly threaded through destructive paths.the flags added at
lib/codex-manager.ts:4187and set inlib/codex-manager.ts:4315/lib/codex-manager.ts:4330prevent prompting right after explicit reset/fresh actions, which keeps the flow predictable.Also applies to: 4315-4315, 4330-4330
4399-4406: backup name sanitization before prompt rendering is solid.
lib/codex-manager.ts:4401strips control sequences before buildingbackupLabel, which reduces terminal injection risk in the recovery confirm text.
Summary
Summary by cubic
Aligns docs and the
codex auth loginfront door with shipped workflows. Adds a startup restore prompt, TTY-only interactivity, and Windows-safe fallthrough to keep login smooth.New Features
isInteractiveLoginMenuAvailable()gates interactivity; non‑TTY uses the minimal flow.getActionableNamedBackupRestores()lists recoverable backups viaPromise.allSettled.Bug Fixes
EPERM/EBUSY/EACCESand wrap assessment in try‑catch so errors skip the prompt and fall through to OAuth.saveAccountswithwithQueuedRetry(getStoragePath())to avoid Windows write‑lock failures.confirmMockfor the restore manager path.Written for commit 477a2fb. Summary will update on new commits.
note: greptile review for oc-chatgpt-multi-auth. cite files like
lib/foo.ts:123. confirm regression tests + windows concurrency/token redaction coverage.Greptile Summary
this pr aligns docs and the
codex auth loginfront door with two shipped features — startup backup recovery and the productized settings split — and adds the code that makes those features discoverable. the code changes are focused: a newisInteractiveLoginMenuAvailable()gate, a startup recovery prompt inrunAuthLogin,getActionableNamedBackupRestoresin storage usingPromise.allSettled, ANSI sanitization of backup names, andwithQueuedRetrywrappingsaveAccountsin the sync-center apply path.key observations:
withQueuedRetryfix insettings-hub.tsis correct for write-lock races; theEPERM/EBUSY/EACCEScatch inrunAuthLoginfalls through to oauth as intended, butrecoveryPromptAttempted = trueis committed before the catch, so a transient av lock permanently and silently suppresses the recovery prompt for the session (only aconsole.debugfires — not user-visible)codex-manager-cli.test.tscoversEBUSYandEPERMthrows fromgetActionableNamedBackupRestoresMockbut notEACCES— theEACCEStest exists only at the storage layer (recovery.test.ts) viaPromise.allSettledrejection, not at therunAuthLoginintegration levelANSI_CONTROL_REstrips SGR sequences and all C0/DEL chars from the backup name, butbackupDir(derived fromCODEX_MULTI_AUTH_DIR) is embedded unsanitized in the confirm prompt — a minor inconsistency given the stated injection-prevention goalConfidence Score: 3/5
withQueuedRetryandPromise.allSettledpatterns are solid. the main blocker isrecoveryPromptAttempted = truebeing set before the windows lock catch — a transient AV file lock silently suppresses recovery for the whole session with no user-visible signal, which is a real UX regression on windows desktops. the missing EACCES integration test incodex-manager-cli.test.tsis a secondary gap given the explicit per-PR rule requiring coverage for all three filesystem error codeslib/codex-manager.ts—recoveryPromptAttemptedplacement relative to the filesystem lock catch block;test/codex-manager-cli.test.ts— missing EACCES mock-rejection test forgetActionableNamedBackupRestoresMockImportant Files Changed
recoveryPromptAttemptedflag is set before the filesystem error catch, so a transient lock permanently silences the prompt;backupDirunsanitized in confirm message despite explicit sanitization of the backup namegetActionableNamedBackupRestoresusingPromise.allSettledfor resilient per-backup assessment; correctly filters by valid/non-exceeding/positive-import;ActionableNamedBackupRecoveriesinterface is clean and testableisInteractiveLoginMenuAvailable()as a tighter TTY gate that composesisNonInteractiveMode()andisTTY(); correctly gates the recovery prompt and replaces the previous bareisTTY()checksaveAccountsinwithQueuedRetry(getStoragePath(), ...)in the sync-center apply path; directly addresses Windows write-lock race on the accounts file during syncrunAuthLogincatch path is missing (only covered at the storage layer in recovery.test.ts)getActionableNamedBackupRestoresdescribe block; EACCES test at storage level viaPromise.allSettledrejection is present and correctFlowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[runAuthLogin starts] --> B[setStoragePath null] B --> C[loadAccounts] C --> D{existingCount == 0 AND interactive TTY AND not suppressed AND not attempted?} D -- No --> K[run OAuth flow] D -- Yes --> E[recoveryPromptAttempted = true] E --> F[getActionableNamedBackupRestores] F -- Windows lock EPERM/EBUSY/EACCES --> G[console.debug, use empty state] F -- success --> H{assessments.length > 0?} G --> K H -- No --> K H -- Yes --> I[confirm: Restore now?] I -- declined --> K I -- confirmed --> J[runBackupRestoreManager] J --> C K[run OAuth flow] --> L{tokenResult type} L -- success --> M[persist account, sync to Codex] L -- cancelled with accounts --> C L -- cancelled no accounts --> N[return 0] L -- failed --> O[return 1] M --> P{add another?} P -- yes --> C P -- no --> N Q[mode fresh or reset] --> R[suppressRecoveryPrompt = true] R --> CComments Outside Diff (10)
lib/codex-manager.ts, line 4078-4103 (link)startup recovery prompt - no windows concurrency reasoning or retry guard
the
runAuthLoginrecovery prompt callsgetActionableNamedBackupRestores, which instorage.tsfans out toPromise.all(backups.map(...assessNamedBackupRestore)). on windows, av tools can hold a read lock on.jsonfiles in~/.codex/multi-auth/backups/. a concurrent read of multiple backup files will race those locks and can throwEBUSYorEPERM— silently swallowing the error and showing the user no restore prompt at all.per org rule, every code change must explain the windows filesystem concurrency defense and reference the regression test that reproduces the race. neither is present here.
additionally, the
backupLabelbelow leaks the raw backup file name (which could include account-derived path fragments) directly into theconfirm()prompt string without scrubbing.Rule Used: What: Every code change must explain how it defend... (source)
lib/codex-manager/settings-hub.ts, line 2949-2966 (link)sync center apply - error message may leak token data and saveAccounts is unguarded
syncAccountStorageFromCodexClican throw with an error whose.messagecomes from deep within token validation or oauth refresh. storing that raw message instatusDetailand rendering it in the sync center UI is a token leakage path — the same surface that rule 637a42e6 requires every patch to address.additionally,
saveAccountshere is called without any windows write-queue guard. if an av tool holds anEBUSYlock on the storage file at the moment apply fires, the write silently fails and the ui continues as if nothing happened.Rule Used: What: Every code change must explain how it defend... (source)
lib/storage.ts, line 1281-1311 (link)Promise.allover backup reads risks EBUSY on windowsPromise.all(backups.map((backup) => assess(backup.name, ...)))fires all backup file reads simultaneously. on windows, av tools serialize directory scans and can hold shared read locks on each.jsonfile in the backups directory. firing several concurrentfs.readFilecalls into the same directory raisesEBUSYorEPERMon the losing reads.the function has no retry shim and callers don't guard it. either serialize the reads sequentially or wrap each
assesscall with the same retry envelope used inwithQueuedRetryfor settings writes.no vitest race regression test is present for this path.
Rule Used: What: Every code change must explain how it defend... (source)
lib/codex-manager.ts, line 4078-4103 (link)Missing try-catch around backup assessment – login crash on Windows file lock
getActionableNamedBackupRestoresdoes aPromise.allover every backup file in~/.codex/multi-auth/backups/. if any individual assessment throws (e.g.EPERM/EBUSYfrom antivirus holding a file on Windows), the entire awaited call rejects and bubbles up unhandled throughrunAuthLogin, crashing the login flow rather than falling through to OAuth.this is a high-probability Windows failure: the recovery prompt is gated on
existingCount === 0, meaning it only fires on first-run or after a reset – exactly the moment a user is most likely to have a newly-placed backup file that an antivirus is still scanning.wrap the assessment call in a try-catch so that any filesystem error is swallowed and the prompt is simply skipped:
lib/storage.ts, line 1298-1311 (link)Promise.allhas no per-backup error isolation – one bad file blocks allPromise.allrejects on the first thrown assessment. on Windows, antivirus or another process can lock any individual backup file at any time. a singleEBUSY/EPERMon one file fails the entiregetActionableNamedBackupRestorescall, making the result useless for the remaining valid backups.use
Promise.allSettledand filter to fulfilled results only:then apply the existing
actionablefilter onassessments. this way one unreadable file doesn't block recovery for the rest.lib/codex-cli/sync.ts, line 555-557 (link)lastCodexCliSyncRunmodule-level state is overwritten by concurrent async operationslastCodexCliSyncRunis a shared mutable module variable. ifpreviewCodexCliSyncandsyncAccountStorageFromCodexCliare both awaited concurrently in the same process (possible if a background live-sync fires while the sync-center preview is refreshing), the lastsetLastCodexCliSyncRuncall wins regardless of which operation actually finished last.the returned
getLastCodexCliSyncRun()snapshot might then reflect a preview run's outcome when the UI was expecting to see the apply outcome, or vice versa.consider stamping each run with an incrementing sequence number and exposing both the latest preview run and the latest apply run separately, or at minimum document the last-write-wins semantic in the interface comment so callers know not to rely on ordering.
lib/codex-manager/settings-hub.ts, line 2957-2960 (link)saveAccountshas no EBUSY/EPERM retry in the sync apply pathsaveAccounts(synced.storage)is called here with no Windows-style retry guard. compare towithQueuedRetryused for every settings write in this same file. on windows, antivirus or another process holding the storage file will throwEBUSY/EPERM, the outercatchswallows it intopreview.status = "error", and the sync result is silently lost. the user has to refresh and apply again.the existing
RETRYABLE_SETTINGS_WRITE_CODESset andwithQueuedRetryalready solve this pattern; the apply path should go through it or at least through the sameenqueueSettingsWritequeue to avoid clobbering concurrent settings writes on the same path.lib/live-account-sync.ts, line 3246-3248 (link)Module-level singleton clobbered by multiple
LiveAccountSyncinstancespublishSnapshot()overwriteslastLiveAccountSyncSnapshotunconditionally. if more than oneLiveAccountSyncinstance is alive simultaneously (plugin mode + a test helper, or a second call tosyncToPathwithout callingstopfirst), the older instance's state is lost on every reload/stop/error event.the sync center reads
getLastLiveAccountSyncSnapshot()to show live-watcher status; if a second instance is created and then stopped before the user opens the sync center, the display will show "idle" even though the original watcher is still running.consider storing snapshots keyed by path, or at minimum assert in
syncToPaththatthis.running === falsebefore overwriting the singleton.lib/codex-manager.ts, line 491-499 (link)EACCESmissing from Windows filesystem fallthrough whitelistthe catch block only swallows
EPERMandEBUSY, but Windows also raisesEACCES(access denied) when the backup directory has restrictive ACLs or is under a folder that antivirus has locked at the directory level. if that happens,getActionableNamedBackupRestoresthrowsEACCES, the conditioncode !== "EPERM" && code !== "EBUSY"is true, and the error is re-thrown — crashing the login flow instead of silently falling through to OAuth.the regression test added for EPERM/EBUSY should be extended with an
EACCEScase to lock in this behavior.lib/codex-manager.ts, line 4383-4403 (link)Transient Windows lock permanently suppresses recovery prompt
recoveryPromptAttempted = trueis set on line 4383 before thetry-catch. When a Windows filesystem lock (EBUSY/EACCES/EPERM) is caught, the flag is alreadytrue, so the recovery prompt is permanently suppressed for the rest of the session — even if the lock was transient (e.g., antivirus briefly holding a file). The only signal is aconsole.debugthat users never see.A user who hits a momentary AV lock on first login would silently bypass recovery and go straight to OAuth, losing access to their named backups until they explicitly navigate to
Restore From Backup.Consider moving
recoveryPromptAttempted = trueinto the path where the assessment actually succeeds and a prompt is shown, or at minimum emit a visible warning (not justconsole.debug) when a filesystem lock causes a skip:Prompt To Fix All With AI
Last reviewed commit: 477a2fb
Context used: